/*
 * Copyright 2023 devaper
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.github.devaper.pigeon.util

import java.nio.charset.*
import java.util.*

/**
 * MediaType, HTTP响应的Content-Type对象展现形式
 *
 * @param type mime类型, 匹配所有请传null
 * @param subtype mime子类型, 匹配所有请传null
 * @param charset 编码
 */
class MediaType(
    val type: String? = null,
    val subtype: String? = null,
    val charset: Charset? = null
) {

    /**
     * 匹配当前MediaType是否和目标相兼容, 支持当前的type/subtype是通配符(target不支持)
     *
     * 示例(*和注释冲突, 使用#代替):
     * - application/json matched application/json
     * - application/#    matched application/json, application/xml
     * - #/#              matched text/plain, application/json
     *
     * @param target 目标MediaType
     * @param ignoreCharset 忽略Charset的匹配
     * @return 匹配结果
     */
    fun match(target: MediaType, ignoreCharset: Boolean = true): Boolean {
        var matched = if (this.type == null) true else this.type == target.type
        if (matched) {
            if (this.subtype != null && this.subtype != target.subtype) {
                matched = false
            }
        }
        if (matched) {
            if (!ignoreCharset && this.charset != target.charset) {
                matched = false
            }
        }
        return matched
    }

    private var _hash: Int? = null

    override fun hashCode(): Int {
        if (_hash == null) {
            _hash = "${MediaType::class.java.canonicalName}#${toString()}".hashCode()
        }
        return _hash!!
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other is MediaType) {
            return match(other, ignoreCharset = false)
        }
        return false
    }

    fun toSimpleString(): String {
        return "${type ?: "*"}/${subtype ?: "*"}"
    }

    override fun toString(): String {
        return toSimpleString() + if (charset != null) "; charset=${charset.name()}" else ""
    }

    companion object {

        /**
         * 根据HTTP的Content-Type解析一个MediaType
         *
         * @return MediaType, 解析失败返回null
         */
        @JvmStatic
        fun parse(contentType: String?): MediaType? {

            contentType ?: return null

            val tokenizer = StringTokenizer(contentType.lowercase(), ";")

            // 取出mime部分
            val mime = if (tokenizer.hasMoreTokens()) {
                tokenizer.nextToken().trim()
            } else {
                return null
            }

            // 取出mime中的两部分
            val types = mime.split("/").takeIf { it.size == 2 } ?: return null

            // 取出编码
            var charset: Charset? = null
            while (tokenizer.hasMoreTokens()) {
                val parts = tokenizer.nextToken().trim().split("=").takeIf { it.size == 2 } ?: continue
                val name = parts[0]
                val value = parts[1]
                if (name == "charset") {
                    try {
                        charset = Charset.forName(value)
                    } catch (ignored: Throwable) {}
                    break
                }
            }

            return MediaType(
                type = types[0].trim().takeIf { it != "*" },
                subtype = types[1].trim().takeIf { it != "*" },
                charset  = charset
            )
        }
    }

}